package org.arclib.examples.light;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;

import org.arclib.ArcGame;
import org.arclib.draw.Color;
import org.arclib.math.Point;
import org.arclib.math.Size;
import org.arclib.x.light.ConvexPolygon;
import org.arclib.x.light.Light;
import org.arclib.x.light.LightBlocker;
import org.arclib.x.light.Penumbra;
import org.arclib.x.light.Shadow;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;

public class LightTest extends ArcGame
{
	ArrayList<LightBlocker> lightBlockers;
	ArrayList<Light> lights;
	IntBuffer rendertex;
	ByteBuffer texdata;

	protected LightTest(String argT, Size argS, boolean argFS)
			throws IOException
	{
		super(argT, argS, argFS);
		// TODO Auto-generated constructor stub

		//
		// load textures
		//
		Penumbra.texture = loader.getTexture("testbin/penumbra.png");
		Light.texture = loader.getTexture("testbin/light.png");

		//
		// setup world data
		//
		setupWorld();

		//
		// initialize dynamic texture
		//
		rendertex = IntBuffer.allocate(1);

		texdata = ByteBuffer.allocate((int) (window.getSize().w
				* window.getSize().h * 4));

		for (int i = 0; i < texdata.capacity(); i++)
		{
			texdata.array()[i] = (byte) 255;
		}

		GL11.glGenTextures(rendertex);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, rendertex.get());
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, 4, window.getWidth(), window
				.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texdata);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
				GL11.GL_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
				GL11.GL_LINEAR);

		// filter the penumbra nicely
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, Penumbra.texture.getID());
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
				GL11.GL_LINEAR_MIPMAP_LINEAR);
		GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
				GL11.GL_LINEAR);

	}

	/**
	 * Setup some blockers and some lights
	 **/
	void setupWorld()
	{
		ArrayList<Point> l1 = new ArrayList<Point>();
		l1.add(new Point(-10, -10));
		l1.add(new Point(10, -10));
		l1.add(new Point(10, 10));
		l1.add(new Point(-10, 10));

		// small box
		lightBlockers.add(new LightBlocker(new Point(225, 220), ConvexPolygon
				.fromVertices(l1)));

		ArrayList<Point> l2 = new ArrayList<Point>();
		l2.add(new Point(-20, -20));
		l2.add(new Point(0, -30));
		l2.add(new Point(20, -20));
		l2.add(new Point(20, 0));
		l2.add(new Point(0, 20));
		l2.add(new Point(-15, 10));

		// some polygon
		lightBlockers.add(new LightBlocker(new Point(450, 360), ConvexPolygon
				.fromVertices(l2)));

		ArrayList<Point> l3 = new ArrayList<Point>();
		l3.add(new Point(-120, -10));
		l3.add(new Point(300, -10));
		l3.add(new Point(300, 10));
		l3.add(new Point(-120, 10));

		// rectangle that's much longer than wide
		lightBlockers.add(new LightBlocker(new Point(150, 100), ConvexPolygon
				.fromVertices(l3)));

		ArrayList<Point> l4 = new ArrayList<Point>();
		l4.add(new Point(80, -80));
		l4.add(new Point(100, -70));
		l4.add(new Point(-70, 100));
		l4.add(new Point(-80, 80));

		// diagonal line
		lightBlockers.add(new LightBlocker(new Point(300, 300), ConvexPolygon
				.fromVertices(l4)));

		// this first light will move with the mouse cursor
		lights.add(new Light(Point.Zero, Color.White, 1024, 10));

		lights.add(new Light(new Point(350, 330), Color.Green, 512));
		lights.add(new Light(new Point(270, 260), Color.Blue, 512));
		lights.add(new Light(new Point(200, 400), Color.Yellow, 1024));
		lights.add(new Light(new Point(500, 50), Color.Red, 512));
		lights.add(new Light(new Point(450, 50), Color.Green, 512));
		lights.add(new Light(new Point(475, 75), Color.Blue, 512));
	}

	public void process()
	{
		// light 0 follows the mouse
		lights.get(0).setPosition(new Point(Mouse.getX(), Mouse.getY()));

		//
		// accumulate lighting in a texture
		//
		GL11.glClearColor(0, 0, 0, 0);
		GL11.glViewport(0, 0, window.getWidth(), window.getHeight());
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);

		for (Light light : lights)
		{
			// clear alpha to full visibility
			GL11.glColorMask(false, false, false, true);
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

			// write shadow volumes to alpha
			GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
			GL11.glDisable(GL11.GL_TEXTURE_2D);
			GL11.glColor4d(0., 0., 0., 1.);
			for (LightBlocker blocker : lightBlockers)
			{
				Shadow.renderShadow(light, blocker);
			}

			// draw light
			GL11.glColorMask(true, true, true, false);
			GL11.glBlendFunc(GL11.GL_ONE_MINUS_DST_ALPHA, GL11.GL_ONE);
			draw.image(Light.texture, light.getPosition(), new Size(2 * light
					.getOuterRadius(), 2 * light.getOuterRadius()), Point.Zero,
					0, light.getColor());
		}

		//
		// copy lighting into texture
		//
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, rendertex.get(0));
		GL11.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB8, 0, 0, window
				.getWidth(), window.getHeight(), 0);

		//
		// render regular scene
		//
		GL11.glViewport(0, 0, window.getWidth(), window.getHeight());
		GL11.glClearColor(1, 1, 1, 0);
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

		for (LightBlocker blocker : lightBlockers)
			blocker.draw();

		//
		// apply lighting by rending light texture on top
		//
		GL11.glBlendFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO);
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, rendertex.get(0));
		Color.White.setGLColor();
		GL11.glBegin(GL11.GL_QUADS);
		GL11.glTexCoord2d(0., 1.);
		GL11.glVertex2f(0, 0);
		GL11.glTexCoord2d(0., 0.);
		GL11.glVertex2f(0, window.getHeight());
		GL11.glTexCoord2d(1., 0.);
		GL11.glVertex2f(window.getWidth(), window.getHeight());
		GL11.glTexCoord2d(1., 1.);
		GL11.glVertex2f(window.getWidth(), 0);
		GL11.glEnd();

		//
		// render lights on top, so they're clearly visible
		//
		GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
		for (Light light : lights)
			light.draw();
	}

	/**
	 * @param args
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws IOException,
			InterruptedException
	{
		// TODO Auto-generated method stub
		LightTest t = new LightTest("Light Test", Size.d640x480, false);
		t.run();
	}

}
